﻿//#define SHOW_INVALIDATE
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace aboveXWords
{
  public sealed partial class Canvas : Panel
  {
    public Canvas(MainForm mainForm)
    {
      MainForm = mainForm;
      InitializeComponent();
      DoubleBuffered = true;
      hScrollBar.Value = 0;
      vScrollBar.Value = 0;
      MouseWheel += CanvasMouseWheel;
      hScrollBar.Scroll += HScrollBarScroll;
      vScrollBar.Scroll += VScrollBarScroll;
      zoomComboBox.GotFocus += ZoomComboBoxEnabledChanged;
      zoomComboBox.SelectedIndexChanged += ZoomComboBoxSelectedIndexChanged;
      Zoom = 1.0f;
    }

    private void CanvasMouseWheel(object sender, MouseEventArgs e)
    {
      if (e.Delta == 0) return;
      int scrollAmount = e.Delta/120;
      int value = vScrollBar.Value - vScrollBar.SmallChange*scrollAmount;
      value = Math.Max(value, vScrollBar.Minimum);
      vScrollBar.Value = Math.Max(vScrollBar.Minimum, Math.Min(value, vScrollBar.Maximum - vScrollBar.LargeChange));
      Invalidate();
    }

    private void HScrollBarScroll(object sender, ScrollEventArgs e)
    {
      Invalidate();
    }

    private void VScrollBarScroll(object sender, ScrollEventArgs e)
    {
      Invalidate();
    }

    private static void ZoomComboBoxEnabledChanged(object sender, EventArgs e)
    {
      var zoomComboBox = sender as ComboBox;
      if (zoomComboBox == null) return;
      zoomComboBox.SelectionLength = 0;
      zoomComboBox.SelectionStart = 100;
    }

    private void ZoomComboBoxSelectedIndexChanged(object sender, EventArgs e)
    {
      var comboBox = sender as ComboBox;
      if (comboBox == null) return;
      var selectedZoom = comboBox.SelectedItem as string;
      if (selectedZoom == null) return;
      int zoom;
      if (Int32.TryParse(selectedZoom.Substring(0, 3), out zoom))
        Zoom = zoom/100f;
      Parent.Select();
      comboBox.SelectionLength = 0;
    }

#if SHOW_INVALIDATE
    private readonly List<Brush> m_brushes = new List<Brush>
                                    {
                                      Brushes.Red,
                                      Brushes.Green,
                                      Brushes.Blue
                                    };
    private int m_br;
#endif

    public void InvalidateCanvas(RectangleF rectangle)
    {
      Rectangle r = Rectangle.Round(rectangle.WorldCoordinates(GetGraphics()));
#if SHOW_INVALIDATE
      GetGraphics().FillRectangle(m_brushes[m_br++], r);
      if (m_br == m_brushes.Count) m_br = 0;
#endif
      Invalidate(r);
    }

    public void InvalidateCanvas(Region rectangle)
    {
#if SHOW_INVALIDATE
      GetGraphics().FillRegion(m_brushes[m_br++], rectangle);
      if (m_br == m_brushes.Count) m_br = 0;
#endif
      rectangle.Transform(GetGraphics().Transform);
      Invalidate(rectangle);
    }

    #region - Zoom 'n' Scroll -

    private float m_zoom;

    public float Zoom
    {
      get { return m_zoom; }
      set
      {
        m_zoom = value;
        if (CanvasBounds.IsEmpty) return;
        AdjustCanvasBounds();
        Invalidate();
      }
    }

    public PointF Origo
    {
      get { return new PointF(- hScrollBar.Value, - vScrollBar.Value); }
    }

    public RectangleF CanvasBounds { get; set; }

    public MainForm MainForm { get; set; }

    private RectangleF CanvasRectangle
    {
      get
      {
        return new RectangleF(ClientRectangle.X,
                              ClientRectangle.Y,
                              ClientRectangle.Width - SystemInformation.HorizontalScrollBarHeight,
                              ClientRectangle.Height - SystemInformation.VerticalScrollBarWidth);
      }
    }

    public void Draw(Graphics graphics)
    {
      if (CanvasBounds.IsEmpty)
        AdjustCanvasBounds(CanvasRectangle);
      graphics.TranslateTransform(Origo.X, Origo.Y);
      graphics.ScaleTransform(Zoom, Zoom);
#if SHOW_INVALIDATE
      graphics.FillRectangle(m_brushes[m_br++], e.Graphics.ClipBounds);
      if (m_br == m_brushes.Count) m_br = 0;
#endif
    }

    private void AdjustCanvasBounds(RectangleF canvasBounds)
    {
      if (CanvasBounds == canvasBounds) return;
      CanvasBounds = canvasBounds;
      AdjustCanvasBounds();
    }

    private void AdjustCanvasBounds()
    {
      if (MainForm.WindowState == FormWindowState.Minimized) return;
      Graphics graphics = CreateGraphics();
      graphics.ScaleTransform(Zoom, Zoom);
      RectangleF canvasBounds = CanvasBounds.WorldCoordinates(graphics);

      hScrollBar.SmallChange = (int) (CanvasRectangle.Width/20);
      hScrollBar.LargeChange = (int) (CanvasRectangle.Width/10);
      hScrollBar.Maximum = (int) (canvasBounds.Right - CanvasRectangle.Width + hScrollBar.LargeChange);
      hScrollBar.Minimum = (int) (canvasBounds.Left);

      vScrollBar.SmallChange = (int) (CanvasRectangle.Height/20);
      vScrollBar.LargeChange = (int) (CanvasRectangle.Height/10);
      vScrollBar.Maximum = (int) (canvasBounds.Bottom - CanvasRectangle.Height + vScrollBar.LargeChange);
      vScrollBar.Minimum = (int) (canvasBounds.Top);
    }

    protected override void OnResize(EventArgs e)
    {
      base.OnResize(e);
      if (CanvasBounds.IsEmpty) return;
      CanvasBounds = RectangleF.Union(CanvasBounds, CanvasRectangle);
      AdjustCanvasBounds();
    }

    internal void AdjustCanvasBounds(PointF mouseLocation)
    {
      // Increase the canvas size if the mouse is holding any shape(s) and
      // the bound of the hold shape(s) is outside the current canvas.
      //RectangleF bounds = RectangleF.Union(CanvasBounds, selectedShapes.Bound.GetBounds(GetGraphics()));
      //AdjustCanvasBounds(RectangleF.Union(bounds, CanvasRectangle));
    }

    internal PointF PageCoordinates(PointF point)
    {
      return point.PageCoordinates(GetGraphics());
    }

    internal PointF WorldCoordinates(PointF point)
    {
      return point.WorldCoordinates(GetGraphics());
    }

    internal Graphics GetGraphics()
    {
      Graphics graphics = CreateGraphics();
      graphics.TranslateTransform(Origo.X, Origo.Y);
      if (Zoom != 0.0)
        graphics.ScaleTransform(Zoom, Zoom);
      return graphics;
    }

    #endregion
  }

  public static class CoordinateConversion
  {
    #region Convert to World Coordinates

    public static RectangleF WorldCoordinates(this RectangleF rectangle, Graphics graphics)
    {
      PointF upperLeft = rectangle.Location.WorldCoordinates(graphics);
      PointF lowerRight = new PointF(rectangle.Right, rectangle.Bottom).WorldCoordinates(graphics);
      return RectangleF.FromLTRB(upperLeft.X, upperLeft.Y, lowerRight.X, lowerRight.Y);
    }

    internal static PointF WorldCoordinates(this PointF point, Graphics graphics)
    {
      var points = new[] {point};
      graphics.TransformPoints(CoordinateSpace.Page, CoordinateSpace.World, points);
      return points[0];
    }

    #endregion

    #region Convert to World Coordinates

    public static RectangleF PageCoordinates(this RectangleF rectangle, Graphics graphics)
    {
      PointF upperLeft = rectangle.Location.PageCoordinates(graphics);
      PointF lowerRight = new PointF(rectangle.Right, rectangle.Bottom).PageCoordinates(graphics);
      return RectangleF.FromLTRB(upperLeft.X, upperLeft.Y, lowerRight.X, lowerRight.Y);
    }

    internal static PointF PageCoordinates(this PointF point, Graphics graphics)
    {
      var points = new[] {point};
      graphics.TransformPoints(CoordinateSpace.World, CoordinateSpace.Page, points);
      return points[0];
    }

    #endregion
  }
}